package cn.itcast.hotel.service.impl;

import cn.itcast.hotel.mapper.HotelMapper;
import cn.itcast.hotel.pojo.Hotel;
import cn.itcast.hotel.pojo.HotelDoc;
import cn.itcast.hotel.pojo.PageResult;
import cn.itcast.hotel.pojo.RequestParams;
import cn.itcast.hotel.service.IHotelService;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.sun.deploy.panel.ITreeNode;
import org.apache.commons.lang3.StringUtils;

import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.xcontent.XContentType;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.SuggestBuilders;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@Service
public class HotelService extends ServiceImpl<HotelMapper, Hotel> implements IHotelService {

    @Autowired
    private RestHighLevelClient client;

    /**
     * 搜索分页基础版
     *
     * @param requestParams 请求参数对象,包含了用户输入的关键字
     * @return 返回页面结果
     */
    @Override
    public PageResult search(RequestParams requestParams) {
        try {
            //创建请求对象
            SearchRequest request = new SearchRequest("hotel");
            //将查询条件封装到方法中,条件过多
            buildQuery(requestParams, request);
            //分页
            int page = requestParams.getPage();
            int size = requestParams.getSize();
            request.source().from((page - 1) * size).size(size);

            //发送请求
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            //解析响应
            return handleResponse(response);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 通过聚合获取酒店数据
     *
     * @param params 请求参数
     * @return 返回一个装载聚合数据的map集合
     */
    @Override
    public Map<String, List<String>> getFilters(RequestParams params) {
        try {
            //创建请求对象
            SearchRequest request = new SearchRequest("hotel");
            //创建source对象
            SearchSourceBuilder source = request.source();
            //调用buildQuery方法,传参数
            //构建查询条件,锁定聚合的范围
            buildQuery(params, request);
            //设置聚合的size
//            source.size(0);
            //实现聚合
            buidAggregation(request);
            //发出请求
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            //解析响应
            //创建一个map集合
            Map<String, List<String>> result = new HashMap<>();
            Aggregations aggregations = response.getAggregations();

            //根据品牌的名称获取品牌结果
            List<String> brandList = getAggByName(aggregations, "brandAgg");
            //将品牌集合存进map集合中
            result.put("brand", brandList);

            //城市聚合
            List<String> cityList = getAggByName(aggregations, "cityAgg");
            result.put("city", cityList);

            //星级聚合
            List<String> starList = getAggByName(aggregations, "starAgg");
            result.put("starName", starList);

            //将map集合返回
            return result;
        } catch (IOException e) {
            //e.printStackTrace();
            //运行时期异常,到时全局异常处理
            throw new RuntimeException(e);
        }

    }

    /**
     * 实现自动补全功能
     *
     * @param key 用户搜索拼音
     * @return 返回一个list集合
     */
    @Override
    public List<String> suggetions(String key) {


        try {
            //创建请求对象
            SearchRequest request = new SearchRequest("hotel");
            //创建source对象
            SearchSourceBuilder source = request.source();
            //添加查询条件
            source.suggest(new SuggestBuilder().addSuggestion("mySuggestion",
                    SuggestBuilders.completionSuggestion("suggestion")
                            .prefix(key)
                            .size(10)));
            //调用客户端发起请求,得到响应对象
            SearchResponse response = client.search(request, RequestOptions.DEFAULT);
            //解析请求
            Suggest suggest = response.getSuggest();
            //根据补全查询名称获取补全信息
            CompletionSuggestion suggestion = suggest.getSuggestion("mySuggestion");

            //获取options
            List<CompletionSuggestion.Entry.Option> options = suggestion.getOptions();
            //创建一个新的集合
            List<String> list = new ArrayList<>();
            //遍历集合
            for (CompletionSuggestion.Entry.Option option : options) {
                String text = option.getText().toString();
                //将内容添加到创建的集合中,返回集合
                list.add(text);
            }
            //返回集合
            return list;
        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException();
        }
    }

    /**
     * 根据聚合名称获取聚合的结果
     *
     * @param aggregations 聚合
     * @param aggName      聚合名
     * @return
     */
    private List<String> getAggByName(Aggregations aggregations, String aggName) {
        // 4.1.根据聚合名称获取聚合结果
        Terms terms = aggregations.get(aggName);
        // 4.2.获取buckets
        List<? extends Terms.Bucket> buckets = terms.getBuckets();
        List<String> list = new ArrayList<>();
        //遍历集合
        for (Terms.Bucket bucket : buckets) {
            String key = bucket.getKeyAsString();
            list.add(key);
        }
        return list;
    }

    /**
     * 实现聚合
     * 添加聚合的条件
     *
     * @param request 搜索请求对象
     */
    private void buidAggregation(SearchRequest request) {
        //分别添加 品牌 brand  , city 城市 , starName  星级 聚合
        request.source().aggregation(AggregationBuilders
                .terms("brandAgg") //品牌聚合的名字
                .field("brand") //字段名
                .size(100) //聚合的条数
        );
        //城市 , 星级 同理
        request.source().aggregation(AggregationBuilders
                .terms("cityAgg") //城市聚合名
                .field("city") //字段名为城市
                .size(100)); //聚合的条数

        //星级聚合
        request.source().aggregation(AggregationBuilders
                .terms("starAgg") //星级聚合的名字
                .field("starName") //指定星级字段
                .size(100)); //聚合条数100条

    }


    /**
     * 添加查询条件
     *
     * @param params  请求参数
     * @param request 搜索请求对象
     */
    private void buildQuery(RequestParams params, SearchRequest request) {
        //构建布尔查询
        BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
        //创建source对象
        SearchSourceBuilder source = request.source();
        String key = params.getKey();
        //判断key是否为空
        if (StringUtils.isNotBlank(key)) {
            //给关键字添加高亮效果
            //创建高亮显示对象
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            //起始
            highlightBuilder.preTags("<em>").postTags("</em>").field("name").requireFieldMatch(false);
            source.highlighter(highlightBuilder);
            //否则根据关键字查询数据
            boolQuery.must(QueryBuilders.matchQuery("all", key));

        } else {
            //关键字为空就查询所有数据
            boolQuery.must(QueryBuilders.matchAllQuery());
        }


        //根据city城市查询数据
        if (StringUtils.isNotBlank(params.getCity())) {
            boolQuery.filter(QueryBuilders.termQuery("city", params.getCity()));
        }

        if (StringUtils.isNotBlank(params.getBrand())) {
            boolQuery.filter(QueryBuilders.termQuery("brand", params.getBrand()));
        }


        if (StringUtils.isNotBlank(params.getStarName())) {
            boolQuery.filter(QueryBuilders.termQuery("starName", params.getStarName()));
        }

        //将最高价格和最低价格拿出来
        Integer maxPrice = params.getMaxPrice();//最高价格
        Integer minPrice = params.getMinPrice();//最低价格
        if (maxPrice != null && minPrice != null) {
            boolQuery.filter(QueryBuilders.rangeQuery("price").gte(minPrice).lt(maxPrice));
        }

        //根据地理位置查询我周边的酒店
        String location = params.getLocation();
        //判断是否得到经纬度
        if (StringUtils.isNotBlank(location)) {
            //如果不为空,则进行排序,根据距离的公里数进行升序排序
            source.sort(SortBuilders.geoDistanceSort("location", new GeoPoint(location)) //坐标点为location
                    .order(SortOrder.ASC)//根据距离升序排序
                    .unit(DistanceUnit.KILOMETERS)); //距离单位为公里
        }
        //添加怕排序功能
        String sortBy = params.getSortBy();
        if (sortBy.equals("score")) {
            source.sort(SortBuilders.fieldSort("score").order(SortOrder.DESC));
        } else if (sortBy.equals("price")) {
            source.sort(SortBuilders.fieldSort("price").order(SortOrder.ASC));
        }
        //添加算分函数
        FunctionScoreQueryBuilder functionScoreQueryBuilder = QueryBuilders.functionScoreQuery(
                boolQuery,
                new FunctionScoreQueryBuilder.FilterFunctionBuilder[]{
                        new FunctionScoreQueryBuilder.FilterFunctionBuilder(
                                QueryBuilders.termQuery("isAD", true),
                                ScoreFunctionBuilders.weightFactorFunction(10)
                        )
                }
        );


        source.query(functionScoreQueryBuilder);
    }

    /**
     * 解析请求
     *
     * @param response 响应对象
     * @return
     */
    private PageResult handleResponse(SearchResponse response) {
        SearchHits hits = response.getHits();
        //获取总条数
        long total = hits.getTotalHits().value;
        //文档数组
        SearchHit[] searchHits = hits.getHits();

        List<HotelDoc> docs = new ArrayList<>();
        //遍历
        for (SearchHit hit : searchHits) {
            //获取文档source
            String json = hit.getSourceAsString();
            //反序列化
            HotelDoc hotelDoc = JSON.parseObject(json, HotelDoc.class);

            //需要显示酒店距离我们所在坐标的距离
            //获取排序的值
            Object[] values = hit.getSortValues();
            //判断该数组是否为空
            if (values.length > 0) {
                //有数据则显示在页面中
                Object sortValue = values[0];
                //将得到的结果赋值给HotelDoc中的字段distance
                hotelDoc.setDistance(sortValue);
            }
            //解析高亮
            //获取高亮结果
            Map<String, HighlightField> map = hit.getHighlightFields();
            //判断该map集合是否为空
            if (!CollectionUtils.isEmpty(map)) {
                //根据字段获取高亮结果
                HighlightField highlightField = map.get("name");
                if (highlightField != null) {
                    //获取高亮值
                    String name = highlightField.getFragments()[0].toString();
                    //覆盖高亮结果
                    hotelDoc.setName(name);
                }
            }
            //将对象放入集合
            docs.add(hotelDoc);
        }
        //封装返回
        return new PageResult(total, docs);
    }


    /**
     * 收到消息后添加 , 新增酒店信息
     * @param id 酒店id
     */
    @Override
    public void insertHotel(Long id) {
        try {
            // 0.根据id查询酒店数据
            Hotel hotel = getById(id);
            // 转换为文档类型
            HotelDoc hotelDoc = new HotelDoc(hotel);

            // 1.准备Request对象
            IndexRequest request = new IndexRequest("hotel").id(hotel.getId().toString());
            // 2.准备Json文档
            request.source(JSON.toJSONString(hotelDoc), XContentType.JSON);
            // 3.发送请求
            client.index(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 收到消息后删除索引库中酒店信息
     * @param id 酒店id
     */
    @Override
    public void deleteHotel(Long id) {
        try {
            //创建删除请求对象
            DeleteRequest request = new DeleteRequest("hotel", id.toString());
            //发送请求
            DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);

            //解析结果
            System.out.println(response.getResult());
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
